package com.notetool.dataprovider.flatfile;

import java.io.File;


// DAns note listener faudrait ajouter un note destroyed event pour enlever tout

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.TreeSet;
import java.util.regex.Pattern;

import com.notetool.dataprovider.NoteDataProvider;
import com.notetool.dataprovider.NoteDataValidator;
import com.notetool.model.Note;
import com.notetool.model.NoteEventListener;
import com.notetool.model.NoteSet;
import com.notetool.util.FileHelper;
import com.notetool.util.StringHelper;
import com.notetool.util.config.AppConfig;

public class FlatFileNoteDataProvider extends NoteDataProvider implements NoteEventListener
{
    
    ////////////////////
    // Members
    private File storePath;
    private HashMap<String, Note> baseSet;
    private HashMap<String, NoteSet> tagSets;
    private FlatFileNoteDataValidator validator;
    
    ////////////////////
    // Constructors
    public FlatFileNoteDataProvider ()
    {
        baseSet = new HashMap<String, Note> ();
        validator = new FlatFileNoteDataValidator ();
        
        initStorePath ();
        buildNotesFromDirectory (storePath);
    }
    
    ////////////////////
    // Accessors

    
    ////////////////////
    // NoteDataProvider implementation
    public NoteSet getNotesFromTag (String tag)
    {
        if (tag == null)
            return new NoteSet ();
        
        // If no wildcard, return the NoteSet right away
        if (!tag.contains("*"))
             return new NoteSet (tagSets.get(tag));
        
        // Get the compiled pattern
        Pattern pattern = StringHelper.makeRegExWildCard (tag, "*");
        
        return combinedMatchingTags (pattern);
    }
    
    public NoteSet getNotesFromId (String id)
    {
        if (id == null)
            return new NoteSet ();
        
        // If no wildcard, return the NoteSet right away
        if (!id.contains("*"))
        {
            Note note = baseSet.get (id);
            
            NoteSet idSet = new NoteSet ();
            if (note != null)
                idSet.addNote (note);
            
            return idSet;
        }
        
        // Get the compiled pattern
        Pattern pattern = StringHelper.makeRegExWildCard (id, "*");
        
        return combinedMatchingIds (pattern);
    }
    
    public void insertNote (Note note) {} //relay work to private addNote that already exists here and write the note to disk
    public void updateNote (Note note) {}
    public void deleteNote (Note note) {}
    public NoteDataValidator getValidator ()
    {
        return validator;
    }
    
    ////////////////////
    // NoteEventListener implementation
    public void tagAdded (Note note, String tag)
    {
        addNoteToTag (note, tag);
    }
    
    public void tagRemoved (Note note, String tag)
    {
        removeNoteFromTag (note, tag);
    }
    
    ////////////////////
    // Private methods
    private void initStorePath ()
    {
        try
        {
            // Get the data path from config
            String path = AppConfig.getConfig().getSet("/config/data_provider/params/data_path/").getFirstValue ();
            File folder = new File (path);
            
            // If doesn't exist, we create it
            if (!folder.exists ())
                folder.mkdirs();
            
            // If not directory we trigger an exception
            if (!folder.isDirectory ())
                throw new Exception ();
            
            storePath = folder;
        }
        catch (Exception ex)
        {
            throw new FlatFileDataProviderException ("Could not initialize data store.");
        }
    }
    
    private void buildNotesFromDirectory (File path)
    {
        // Get all files in directory
        File[] files = path.listFiles (new NoteFileFilter ());
        
        // Build notes
        for (File file : files)
        {
            Note note = buildNoteFromFile (file);
            
            if (note == null)
                continue;
            
            addNote (note);
        }
    }
    
    private Note buildNoteFromFile (File file)
    {
        if (file == null)
            return null;
        
        try
        {
            // Create note
            Note note = new Note (parseIdFromFile (file));
            note.addTags (parseTagsFromFile (file));
            
            return note;
        }
        catch (FlatFileDataProviderException ex)
        {
            return null;
        }
    }
    
    private String parseIdFromFile (File file)
    {
        // Get file name minus extension
        String id = StringHelper.emptyIfNull (FileHelper.getStrippedName (file)).trim ();
        
        if (!validator.isValidId (id))
            throw new FlatFileDataProviderException ("Invalid Note ID read from data directory.");
        
        return id;
    }
    
    private String[] parseTagsFromFile (File file)
    {
        // Read 1st line
        String line = FileHelper.getLine (file, 0);
        
        // Split it
        String tags[] = StringHelper.split (line);
        
        if (tags == null)
            throw new FlatFileDataProviderException ("Tags string could not be processed.");
        
        // Validate tags
        for (String tag : tags)
        {
            if (!validator.isValidTag (tag))
                throw new FlatFileDataProviderException ("Invalid tag read from data File data.");
        }
        
        return tags;
    }
    
    // This is private because the interfaces that the rest of the classes should use are insert/update/delete. add/remove are only for internal mechanics
    private void addNote (Note note)
    {
        // Add note
        baseSet.put (note.getId (), note);
        addTagsFromNote (note);
        
        // Monitor changes that may happen to this note
        note.addEventListener (this);
    }
    
    private void removeNote (Note note)
    {
        // Remove note
        baseSet.remove (note.getId ());
        removeTagsFromNote (note);
        
        // Stop monitoring changes made to this note
        note.removeEventListener (this);
    }
    
    private void addTagsFromNote (Note note)
    {
        if (note == null)
            return;
        
        TreeSet<String> noteTags = note.getTags ();
     
        // Add note to tags
        for (String tag : noteTags)
            addNoteToTag (note, tag);
    }

    private void addNoteToTag (Note note, String tag)
    {
        NoteSet noteList = tagSets.get (tag);
        
        // If the tag doesn't exist, create it
        if (noteList == null)
        {
            noteList = new NoteSet ();
            tagSets.put (tag, noteList);
        }
            
        // Add note to tag
        noteList.addNote (note); 
    }
    
    private void removeTagsFromNote (Note note)
    {
        if (note == null)
            return;
        
        // Get tags
        TreeSet<String> tagList = note.getTags ();
        
        // Loop through tags
        for (String tag : tagList)
        {
            removeNoteFromTag (note, tag);
        }
    }
    
    private void removeNoteFromTag (Note note, String tag)
    {
        NoteSet noteList = tagSets.get (tag);
       
        if (noteList == null)
            return;
        
        // Remove from tag
        noteList.removeNote (note);
        
        // If the tag no longer contains any Note for this set, clear it
        if (noteList.isEmpty ())
            tagSets.remove (tag);
    }
    
    private NoteSet combinedMatchingTags (Pattern pattern)
    {
        NoteSet combinedNotes = new NoteSet ();
        
        Iterator<Entry<String, NoteSet>> tags = tagSets.entrySet ().iterator ();
        while (tags.hasNext ())
        {
            Entry<String, NoteSet> tag = tags.next ();
            if (pattern.matcher(tag.getKey ()).matches ())
                combinedNotes.union (tag.getValue ());
        }
        
        return combinedNotes;
    }
    
    private NoteSet combinedMatchingIds (Pattern pattern)
    {
        NoteSet combinedNotes = new NoteSet ();
        
        Iterator<Entry<String, Note>> tags = baseSet.entrySet().iterator ();
        while (tags.hasNext ())
        {
            Entry<String, Note> id = tags.next ();
            if (pattern.matcher(id.getKey ()).matches ())
                combinedNotes.addNote (id.getValue ());
        }
        
        return combinedNotes;
    }
    
}
